﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace QUT.Bio.Util {
	/// <summary>
	///		A SparseMatrix is a collection of nested dictionaries provides a lookup table for 
	///		double indexed sparse collections of values.
	///		<para>
	///			Dependant objects may subscribe to the CollectionChanged event to receive 
	///			notifications of additions or removals from the collection.
	///		</para>
	/// </summary>
	/// <typeparam name="First">
	///		The first index type.
	/// </typeparam>
	/// <typeparam name="Second">
	///		The second index type.
	/// </typeparam>
	/// <typeparam name="Value">
	///		The value type.
	/// </typeparam>

	public class SparseMatrix<First, Second, Value>
		: IEnumerable<Triple<First, Second, Value>> {

		private readonly Dictionary<First, Dictionary<Second, Triple<First, Second, Value>>> triples = new Dictionary<First, Dictionary<Second, Triple<First, Second, Value>>>();

		/// <summary>
		/// Event used to notify listeners that the collection has changed.
		/// </summary>

		public event Action<SparseMatrix<First, Second, Value>> CollectionChanged;

		/// <summary>
		/// Used to carry a descrioption of an ItemChanged event, fired by the matrix-style indexer.
		/// </summary>

		public class ItemChangedEventArgs {

			/// <summary>
			/// The first index value of the changed item.
			/// </summary>

			public First First {
				get;
				set;
			}

			/// <summary>
			/// The second index value of the changed item.
			/// </summary>

			public Second Second {
				get;
				set;
			}

			/// <summary>
			/// The previous value of the changed item.
			/// </summary>

			public Value OldValue {
				get;
				set;
			}

			/// <summary>
			/// The new value of the changed item.
			/// </summary>

			public Value NewValue {
				get;
				set;
			}
		};

		/// <summary>
		/// Event used to notify listeners that the value of a single item has been changed by the indexer.
		/// <para>A handler should have a signature like: delegate( SparseMatrix&lt;A,B,C&gt; matrix, A a, B b, C oldValue, C newValue ).</para>
		/// </summary>

		public event Action<SparseMatrix<First, Second, Value>, ItemChangedEventArgs> ItemChanged;

		/// <summary>
		///		Get or set the value at position (first, second) in this sparse matrix.
		///		<para>When updating, if a value already existed we fire ItemChanged; otherwise we fire CollectionChanged and add the new record.</para>
		/// </summary>
		/// <param name="first">
		///		The id of the source node.
		///	</param>
		/// <param name="second">
		///		The id of the destination node.
		///	</param>
		/// <returns>
		///		The edge connecting first to second, or null if not present.
		///	</returns>
		/// <exception cref="System.Collections.Generic.KeyNotFoundException">
		///		The property is retrieved and key does not exist in the collection.
		/// </exception>

		public Value this[First first, Second second] {
			get {
				return triples[first][second].Third;
			}
			set {
				if ( Contains( first, second ) ) {
					Triple<First, Second, Value> triple = triples[first][second];

					if ( !value.Equals( triple.Third ) ) {
						Value oldValue = triple.Third;
						triple.Third = value;
						SignalItemChanged( new ItemChangedEventArgs { 
							First = first, 
							Second = second, 
							OldValue = oldValue, 
							NewValue = value 
						} );
					}
				}
				
				else {
					Add( first, second, value );
				}
			}
		}

		/// <summary>
		///		Adds a new item to the collection, selectively firing the
		///		CollectionChanged event if successful.
		/// </summary>
		/// <param name="first">
		///		The first index value.
		/// </param>
		/// <param name="second">
		///		The second index value.
		/// </param>
		/// <param name="value">
		///		The value to add.
		/// </param>
		/// <exception cref="System.ArgumentNullException">
		///		ArgumentNullException, if first or second is null.
		/// </exception>
		/// <exception cref="System.ArgumentException">
		///		ArgumentException, if an entry already exists for (first,second)
		/// </exception>

		public void Add ( First first, Second second, Value value ) {
			Add( first, second, value, true );
		}

		/// <summary>
		///		Adds a new item to the collection, selectively firing the
		///		CollectionChanged event if successful.
		/// </summary>
		/// <param name="first"></param>
		/// <param name="second"></param>
		/// <param name="value"></param>
		/// <param name="signal"></param>
		/// <exception cref="System.ArgumentNullException">
		///		ArgumentNullException, if first or second is null.
		/// </exception>
		/// <exception cref="System.ArgumentException">
		///		ArgumentException, if an entry already exists for (first,second)
		/// </exception>

		public void Add ( First first, Second second, Value value, bool signal ) {
			Dictionary<Second, Triple<First, Second, Value>> row = null;

			Triple<First, Second, Value> triple = new Triple<First, Second, Value> {
				First = first,
				Second = second,
				Third = value
			};

			if ( triples.ContainsKey( first ) ) {
				row = triples[first];
			}
			else {
				row = new Dictionary<Second, Triple<First, Second, Value>>();
				triples[first] = row;
			}

			if ( row.ContainsKey( second ) ) {
				throw new ArgumentException(
					"Attempting to add duplicate item to collection. See Data for details."
				);
			}

			row[second] = triple;

			SignalCollectionChanged( signal );
		}

		/// <summary>
		///		Adds a sequence of triples to the collection, then (in the event that 
		///		at least one triple was added) triggers the CollectionChanged event.
		/// </summary>
		/// <param name="newTriples">
		///		A sequence of triples to add to the collection.
		///	</param>
		/// <exception cref="System.NullReferenceException">
		///		NullReferenceException if newTriples is null.
		///	</exception>

		public void AddRange ( IEnumerable<Triple<First, Second, Value>> newTriples ) {
			bool changed = false;

			foreach ( var triple in newTriples ) {
				Add( triple.First, triple.Second, triple.Third, false );
				changed = true;
			}

			SignalCollectionChanged( changed );
		}

		/// <summary>
		///		If the collection contains some triples, removes all triples from this 
		///		collection, then fires the CollectionChanged event.
		/// </summary>

		public void Clear () {
			if ( triples.Count > 0 ) {
				triples.Clear();
				SignalCollectionChanged( true );
			}
		}

		/// <summary>
		///		Determines if the collection contains an edge connecting two triples.
		/// </summary>
		/// <param name="first">
		///		The id of the source node.
		///	</param>
		/// <param name="second">
		///		The id of the destination node.
		///	</param>
		/// <returns>
		///		True iff the collection contains an edge connecting triples with ids first and second.
		///	</returns>

		public bool Contains ( First first, Second second ) {
			return triples.ContainsKey( first ) && triples[first].ContainsKey( second );
		}

		/// <summary>
		///		Gets an enumerator that iterates over all triples in the collection.
		/// </summary>
		/// <returns>
		///		An enumerator that iterates over all triples in the collection.
		///	</returns>

		public IEnumerator<Triple<First, Second, Value>> GetEnumerator () {
			foreach ( var x in triples ) {
				foreach ( var y in x.Value ) {
					yield return y.Value;
				}
			}
		}

		/// <summary>
		///		Gets an enumerator that iterates over all triples in the collection.
		/// </summary>
		/// <returns>
		///		An enumerator that iterates over all triples in the collection.
		///	</returns>

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
			foreach ( var x in triples ) {
				foreach ( var y in x.Value ) {
					yield return y.Value;
				}
			}
		}

		/// <summary>
		///		Removes the specified value from the collection then fires the CollectionChanged 
		///		event.
		/// </summary>
		/// <param name="first">
		///		The first index of the value to remove.
		///	</param>
		/// <param name="second">
		///		The second index of the value to remove.
		///	</param>
		/// <exception cref="System.ArgumentNullException">
		///		The node is null or its Id is null.
		///	</exception>

		public void Remove ( First first, Second second ) {
			Remove( first, second, true );
		}

		/// <summary>
		///		Removes the specified node node, then fires the CollectionChanged event.
		/// </summary>
		/// <param name="first">
		///		The first index of the value to remove.
		///	</param>
		/// <param name="second">
		///		The second index of the value to remove.
		///	</param>
		/// <param name="signal">
		///		If true, the collectionChanged event will fire upon successful removal.
		///		Otherwise, it will not.
		///	</param>

		private void Remove ( First first, Second second, bool signal ) {
			if ( triples.ContainsKey( first ) ) {
				var x = triples[first];

				if ( x.ContainsKey( second ) ) {
					x.Remove( second );

					if ( x.Count == 0 ) {
						triples.Remove( first );
					}

					SignalCollectionChanged( signal );
				}
			}
		}

		/// <summary>
		///		Removes all triples that match a supplied predicate from the collection,
		///		then (if something was removed) fires the Collectionchanged event.
		/// </summary>
		/// <param name="where">
		///		A predicate used to filter out triples.
		///	</param>
		/// <exception cref="System.ArgumentNullException">
		///		The node is null or its Id is null.
		///	</exception>

		public void RemoveAll ( Predicate<First, Second, Value> where ) {
			List<Triple<First, Second, Value>> triplesToRemove = new List<Triple<First, Second, Value>>();
			bool changed = false;

			foreach ( var triple in this ) {
				if ( where( triple.First, triple.Second, triple.Third ) ) {
					triplesToRemove.Add( triple );
				}
			}

			foreach ( var triple in triplesToRemove ) {
				Remove( triple.First, triple.Second, false );
				changed = true;
			}

			SignalCollectionChanged( changed );
		}

		/// <summary>
		///		Replaces the contents of the collection with a sequence of triples 
		///		to the collection, then triggers the CollectionChanged event if some 
		///		change has occurred.
		/// </summary>
		/// <param name="newTriples"></param>
		/// <exception cref="System.ArgumentNullException">
		///		newTriples is null or one of the index values is null.
		///	</exception>
		/// <exception cref="System.ArgumentException">
		///		The sequence of triples to be added contains a node with the same Id 
		///		as one already present in the collection.
		///	</exception>

		public void ReplaceAll ( IEnumerable<Triple<First, Second, Value>> newTriples ) {
			bool changed = false;

			if ( triples.Count > 0 ) {
				triples.Clear();
				changed = true;
			}

			foreach ( var triple in newTriples ) {
				Add( triple.First, triple.Second, triple.Third, false );
				changed = true;
			}

			SignalCollectionChanged( changed );
		}

		/// <summary>
		///		Removes all items matching the supplied predicate, then adds all items in a sequence of 
		///		triples, and finally (if something changed) triggers the Collectionchanged event.
		///		<para>
		///			This is basically the same as RemoveAll( where ) followed by AddRange, except we 
		///			only fire CollectionChanged once.
		///		</para>
		/// </summary>
		/// <param name="newTriples">
		///		A sequence of triples to be added to the collection.
		///	</param>
		/// <param name="where">
		///		A predicate used to filter out unwnated triples.
		///	</param>
		/// <exception cref="System.ArgumentNullException">
		///		newTriples is null or one of the node ids is null
		///	</exception>
		/// <exception cref="System.ArgumentException">
		///		The sequence of triples to be added contains a node with the same Id 
		///		as one already present in the collection.
		/// </exception>

		public void ReplaceAll (
			IEnumerable<Triple<First, Second, Value>> newTriples,
			Predicate<First, Second, Value> where
		) {
			var triplesToRemove = new List<Triple<First, Second, Value>>();
			bool changed = false;

			foreach ( var triple in triplesToRemove ) {
				if ( where( triple.First, triple.Second, triple.Third ) ) {
					Remove( triple.First, triple.Second, false );
					changed = true;
				}
			}

			foreach ( var triple in newTriples ) {
				Add( triple.First, triple.Second, triple.Third, false );
				changed = true;
			}

			SignalCollectionChanged( changed );
		}

		/// <summary>
		///		Selectively triggers the CollectionChanged event.
		/// </summary>
		/// <param name="signal">
		///		Determines whether or not to actually fired the event.
		///	</param>

		private void SignalCollectionChanged ( bool signal ) {
			if ( signal && CollectionChanged != null ) {
				CollectionChanged( this );
			}
		}

		/// <summary>
		///		Selectively triggers the ItemChanged event.
		/// </summary>
		/// <param name="args">
		///		The information to broadcast about this item change.
		///	</param>

		private void SignalItemChanged ( ItemChangedEventArgs args ) {
			if ( ItemChanged != null ) {
				ItemChanged( this, args );
			}
		}
	}
}
